package cn.icanci.loopstack.rec.engine.sdk.rule;

import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.rec.common.enums.*;
import cn.icanci.loopstack.rec.engine.sdk.factor.FactorSupport;
import cn.icanci.loopstack.rec.common.aggregation.model.DataSourceDTO;
import cn.icanci.loopstack.rec.common.aggregation.model.DomainDTO;
import cn.icanci.loopstack.rec.common.aggregation.model.StrategyDTO;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngine;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngineManager;
import cn.icanci.loopstack.rec.engine.script.context.RecScriptEngineContext;
import cn.icanci.loopstack.rec.engine.script.wrapper.HttpResponseWrapper;
import cn.icanci.loopstack.rec.engine.sdk.actuator.RuleEngineRequest;
import cn.icanci.loopstack.rec.engine.sdk.actuator.RuleEngineResponse;
import cn.icanci.loopstack.rec.engine.sdk.exception.ValidatorException;
import cn.icanci.loopstack.rec.engine.sdk.rule.pool.ScriptExecutorPoolHolder;
import cn.icanci.loopstack.rec.engine.sdk.rule.repository.EngineRepositoryHolder;

import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import javax.annotation.Resource;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/15 23:17
 */
@Service
public final class EngineExecutor {

    private static final Logger      logger          = LoggerFactory.getLogger(EngineExecutor.class);

    @Resource
    private EngineRepositoryHolder   holder;

    @Resource
    private ScriptExecutorPoolHolder scriptExecutorPoolHolder;

    @Resource
    private FactorSupport factorSupport;

    private final RecScriptEngine    recScriptEngine = RecScriptEngineManager.getRecScriptEngine();

    /**
     * 执行入口
     * 
     * @param request request
     * @return 返回执行结果
     */
    public RuleEngineResponse execute(RuleEngineRequest request) {
        try {
            logger.info("[EngineExecutor][execute] request:{}", JSONUtil.toJsonStr(request));
            // 1.数据基本校验
            validator(request);

            // 2.获取策略
            String domainCode = request.getDomainCode();
            String sceneCode = request.getSceneCode();

            DomainDTO domain = holder.getDomain(domainCode);
            if (domain == null) {
                return RuleEngineResponse.fail(ErrorMessageFormat.domainNotFound(domainCode));
            }
            StrategyDTO strategy = holder.getStrategy(domainCode, sceneCode);

            if (strategy == null) {
                return RuleEngineResponse.fail(ErrorMessageFormat.domainAndSceneNotFound(domainCode, sceneCode));
            }
            // 3.聚合数据
            Bindings bindings = mergeDataSource(request, domain, strategy);

            // 4.执行
            RuleTypeEnum ruleType = RuleTypeEnum.valueOf(strategy.getRuleType());
            Object executorResult = null;
            RuleModeEnum ruleMode = RuleModeEnum.valueOf(strategy.getRuleMode());
            switch (ruleType) {
                case LIST:
                    executorResult = ruleListExecutor(strategy.getDomainCode(), strategy.getRuleListInfo(), ruleMode, bindings);
                    break;
                // TODO 一期版本不处理Tree
                //                case TREE:
                //                    executorResult = ruleTreeExecutor(strategy.getDomainCode(), strategy.getRuleTreeInfo(), ruleMode, bindings);
                //                    break;
                default:
                    // no op
            }
            // 5.返回执行结果
            RuleEngineResponse response = RuleEngineResponse.success(executorResult, bindings);
            logger.info("[EngineExecutor][execute] response:{}", JSONUtil.toJsonStr(response));
            return response;
        } catch (Throwable e) {
            logger.info("[EngineExecutor][execute] error:{}", e.getMessage());
            return RuleEngineResponse.fail(e.getMessage());
        }
    }

    /**
     * List 执行
     * 
     * @param domainCode domainCode
     * @param ruleListInfo ruleListInfo
     * @param ruleMode ruleMode
     * @param bindings bindings
     * @return 返回执行结果
     */
    private Object ruleListExecutor(String domainCode, StrategyDTO.RuleListInfo ruleListInfo, RuleModeEnum ruleMode, Bindings bindings) {
        // List 配置模式

        // [condition1 ---------] ----------|
        // |sc1.1-----                      |
        //        &                         |
        // |sc1.2-----                      |
        // [condition2 ---------]               |
        // |sc2.1-----                      |
        //        &                         |
        // |sc2.2-----                      |
        //        &                         |
        // |sc2.3-----                      |

        // condition1 和 condition2            是或的关系
        // condition1 中 sc1.1 和 sc1.2        是且的关系
        // condition2 中 sc2.1、sc2.2、sc2.3   是且的关系
        // 如果每一组的最后一个cell具备孩子节点，则继续执行孩子节点

        // 判断执行模式，简单还是复杂
        List<StrategyDTO.Condition> conditions = ruleListInfo.getConditions();
        // 模式执行
        return doConditionsRun(domainCode, ruleMode, bindings, conditions);
    }

    /**
     * Tree 执行
     * 
     * @param domainCode domainCode
     * @param ruleTreeInfo ruleTreeInfo
     * @param ruleMode ruleMode
     * @param bindings bindings
     * @return 返回执行结果
     */
    private Object ruleTreeExecutor(String domainCode, StrategyDTO.RuleTreeInfo ruleTreeInfo, RuleModeEnum ruleMode, Bindings bindings) {
        List<List<StrategyDTO.Condition>> conditionGroups = ruleTreeInfo.getConditionGroups();
        for (List<StrategyDTO.Condition> conditionGroup : conditionGroups) {
            // 单元组执行
            Object ret = doConditionsRun(domainCode, ruleMode, bindings, conditionGroup);
            // 返回值是否是boolean
            boolean isBoolean = ret instanceof Boolean;
            // 如果不是布尔值，说明命中中断，返回中断
            if (!isBoolean) {
                return ret;
            } else if (((Boolean) ret)) {
                // 是布尔值并且为true，说符合全组条件，返回
                return true;
            }
        }
        // 一个都没执行到，返回false
        return false;
    }

    /**
     * 模式执行
     *
     * @param domainCode domainCode
     * @param ruleMode ruleMode
     * @param bindings bindings
     * @param conditions conditions
     * @return 返回执行结果
     */
    private Object doConditionsRun(String domainCode, RuleModeEnum ruleMode, Bindings bindings, List<StrategyDTO.Condition> conditions) {
        for (StrategyDTO.Condition condition : conditions) {
            // 组内
            Object ret = matchGroup(condition.getGroup(), domainCode, ruleMode, bindings);
            // 返回值是否是boolean
            boolean isBoolean = ret instanceof Boolean;
            // 如果不是布尔值，说明命中中断，返回中断
            if (!isBoolean) {
                return ret;
            } else if (((Boolean) ret)) {
                // 是布尔值并且为true，说符合全组条件，返回
                return true;
            }
        }
        // 一个都没执行到，返回false
        return false;
    }

    /**
     * 是否命中此组
     *
     * @param group group
     * @param domainCode domainCode
     * @param ruleMode ruleMode
     * @param bindings bindings
     * @return 返回是否命中或者中断结果
     */
    private Object matchGroup(List<StrategyDTO.ConditionCell> group, String domainCode, RuleModeEnum ruleMode, Bindings bindings) {
        if (CollectionUtils.isEmpty(group)) {
            return Boolean.FALSE;
        }
        for (StrategyDTO.ConditionCell sc : group) {
            OperatorEnum operator = OperatorEnum.valueOf(sc.getOperator());
            boolean match = factorSupport.getFactor(operator).match(bindings, sc, domainCode);
            // 命中中断条件的返回值
            if (match && ruleMode == RuleModeEnum.COMPLEX && InterruptEnum.valueOf(sc.getInterrupt()) == InterruptEnum.TRUE) {
                // 执行中断
                return sc.getReturnVal();
            }
            if (!match) {
                return Boolean.FALSE;
            }
        }
        StrategyDTO.ConditionCell last = group.get(group.size() - 1);
        if (CollectionUtils.isEmpty(last.getChildren())) {
            return Boolean.TRUE;
        }
        // 执行孩子节点 List结构没有孩子节点
        return matchGroup(last.getChildren(), domainCode, ruleMode, bindings);
    }

    /**
     * 聚合数据
     * 
     * @param request request
     * @param domain domain
     * @param strategy strategy
     * @return 返回聚合的数据
     * @throws ScriptException 脚本执行异常
     */
    private Bindings mergeDataSource(RuleEngineRequest request, DomainDTO domain, StrategyDTO strategy) throws ScriptException, ExecutionException, InterruptedException,
                                                                                                        TimeoutException {
        Map<String, Object> parameters = request.getParameters();

        SimpleBindings bindings = new SimpleBindings();
        String dataSourceUuid = strategy.getDataSourceUuid();
        if (StringUtils.isNotBlank(dataSourceUuid)) {
            DataSourceDTO dataSource = holder.getDataSource(domain.getDomainCode(), dataSourceUuid);
            DataSourceTypeEnum dataSourceTypeEnum = DataSourceTypeEnum.valueOf(dataSource.getDataSourceType());
            switch (dataSourceTypeEnum) {
                case SCRIPT:
                    DataSourceDTO.ScriptInfo scriptInfo = dataSource.getScriptInfo();
                    CompiledScript compiledScript = scriptInfo.getCompiledScript();
                    if (compiledScript != null) {
                        Object eval = compiledScript.eval();
                        bindings.putAll(mapperMap(eval, domain, strategy, dataSource));
                    } else {
                        // 线程池
                        FutureTask<RecScriptEngineContext<Object>> task = new FutureTask<>(new ScriptExecutor(scriptInfo, recScriptEngine));
                        scriptExecutorPoolHolder.submit(task);
                        // 等待执行结果
                        RecScriptEngineContext<Object> context = task.get(scriptInfo.getTimeout(), TimeUnit.SECONDS);
                        if (context.isSuccess()) {
                            bindings.putAll(mapperMap(context.getRealRetVal(), domain, strategy, dataSource));
                        } else {
                            throw new IllegalArgumentException(ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(),
                                dataSource.getDataSourceName(), context.getThrowable().getMessage()));
                        }
                    }
                    break;
                case HTTP:
                    DataSourceDTO.HttpInfo httpInfo = dataSource.getHttpInfo();
                    HttpResponseWrapper wrapper = recScriptEngine.httpEval(HttpRequestTypeEnum.valueOf(httpInfo.getHttpRequestType()), httpInfo.getReqUrl(), httpInfo.getReqParam(),
                        httpInfo.getTimeout());
                    if (wrapper.isSuccess()) {
                        String response = wrapper.getResponse();
                        if (JSONUtil.isJson(response)) {
                            bindings.putAll(wrapper.getResponseForMap());
                        } else {
                            throw new IllegalArgumentException(
                                ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "HTTP执行结果不是Json"));
                        }
                    } else {
                        throw new IllegalArgumentException(ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(),
                            dataSource.getDataSourceName(), wrapper.getException().getMessage()));

                    }
                    break;
                default:
                    // no op
            }
        }
        // 传入的参数优先级最高
        if (parameters != null) {
            bindings.putAll(parameters);
        }
        return bindings;
    }

    /**
     * mapperMap
     * 
     * @param eval eval
     * @param domain domain
     * @param strategy strategy
     * @param dataSource dataSource
     * @return 返回Map
     */
    private Map mapperMap(Object eval, DomainDTO domain, StrategyDTO strategy, DataSourceDTO dataSource) {
        if (eval instanceof Map) {
            return (Map<? extends String, ?>) eval;
        } else if (eval instanceof String) {
            String str = String.valueOf(eval);
            boolean isJson = JSONUtil.isJson(str);
            if (!isJson) {
                throw new IllegalArgumentException(
                    ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "脚本执行结果不是Json"));
            } else {
                return JSONUtil.toBean(str, Map.class);
            }
        } else {
            throw new IllegalArgumentException(
                ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "脚本执行结果不是Map"));
        }
    }

    /**
     * 数据基本校验
     * 
     * @param request request
     */
    private void validator(RuleEngineRequest request) {
        if (request == null) {
            throw new ValidatorException("request is Null!");
        }
        if (StringUtils.isBlank(request.getDomainCode())) {
            throw new ValidatorException("domainCode is empty!");
        }
        if (StringUtils.isBlank(request.getSceneCode())) {
            throw new ValidatorException("sceneCode is empty!");
        }
    }

    /** 执行器  */
    private static class ScriptExecutor implements Callable<RecScriptEngineContext<Object>> {
        /** scriptInfo */
        private final DataSourceDTO.ScriptInfo scriptInfo;
        /** recScriptEngine执行引擎 */
        private final RecScriptEngine          recScriptEngine;

        public ScriptExecutor(DataSourceDTO.ScriptInfo scriptInfo, RecScriptEngine recScriptEngine) {
            this.scriptInfo = scriptInfo;
            this.recScriptEngine = recScriptEngine;
        }

        @Override
        public RecScriptEngineContext<Object> call() throws Exception {
            return recScriptEngine.eval(ScriptTypeEnum.valueOf(scriptInfo.getScriptType()), scriptInfo.getScriptContent());
        }
    }
}
